package lab2;

import java.util.Iterator;

public class MyLinkedList<T> implements MyList<T>, MyStack<T>, MyQueue<T> {

	private static class Node<N> {
		private N body;
		private Node<N> previous;
		private Node<N> next;

		public Node() {
			body = null;
			previous = null;
			next = null;
		}

		public Node(final N o) {
			body = o;
			previous = null;
			next = null;
		}
	}

	private Node<T> head;

	private Node<T> tail;

	private int size;

	public MyLinkedList() {
		head = new Node<T>();
		head.previous = null;
		head.next = null;
		tail = head;
		size = 0;
	}

	public MyLinkedList(final MyList<T> c) {

		this();

		if (c == null) {
			throw new NullPointerException();
		}

		if (c.size() > 0) {
			Iterator<T> iter = c.iterator();
			while (iter.hasNext()) {
				add(iter.next());
			}
			add(iter.next());
		}
	}

	public void add(final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		if (size > 0) {
			tail.next = new Node<T>();
			tail.next.body = e;
			tail.next.previous = tail;
			tail = tail.next;
			size++;
		} else {
			head.body = e;
			size++;
		}
	}

	public void add(final int index, final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		if (index < 0 || index > size) {
			throw new MyIndexOutOfBoundsException("Index out of bounds!");
		}

		Node<T> p;

		switch (index) {

		case 0:
			p = new Node<T>(e);
			p.next = head;
			head.previous = p;
			head = p;
			size++;
			break;

		default:
			p = head;
			for (int i = 0; i < index && p.next != null; i++) {
				p = p.next;
			}
			if (p.next != null) {
				p.next.previous = new Node<T>(e);
				p.next.previous.next = p.next;
				p.next.previous.previous = p;
				p.next = p.next.previous;
				size++;
			} else {
				p.next = new Node<T>(e);
				p.next.previous = p;
				size++;
			}
			break;
		}
	}

	public void addAll(final T[] c) {
		if (c == null) {
			throw new NullPointerException();
		}

		if (c.length > 0) {
			for (T o : c) {
				add(o);
			}
		}
	}

	public void addAll(int index, T[] c) {
		if (c == null) {
			throw new NullPointerException();
		}

		if (index < 0 || index > size) {
			throw new MyIndexOutOfBoundsException("Index out of bounds!");
		}

		if (c.length > 0) {
			MyLinkedList<T> mll = new MyLinkedList<T>();
			mll.addAll(c);

			Node<T> p;

			switch (index) {

			case 0:
				if (!isEmpty()) {
					mll.tail.next = head;
				}
				head.previous = mll.tail;
				if (isEmpty()) {
					tail = mll.tail;
				}
				head = mll.head;				
				size += mll.size;
				break;

			default:
				p = head;
				for (int i = 0; i < index - 1 && p.next != null; i++) {
					p = p.next;
				}
				if (p.next != null) {
					mll.tail.next = p.next;
					p.next.previous = mll.tail;
					mll.head.previous = p;
					p.next = mll.head;
					size += mll.size;
				} else {
					p.next = mll.head;
					mll.head.previous = p;
					tail = mll.tail;
					size += mll.size;
				}
				break;
			}
		}
	}

	public T get(final int index) {
		if (index < 0 || index >= size) {
			throw new MyIndexOutOfBoundsException("Index out of bounds!");
		}

		if (index == 0) {
			return head.body;
		}

		if (index == size - 1) {
			return tail.body;
		}

		Node<T> p = head;
		for (int i = 0; i < index; i++) {
			p = p.next;
		}
		return p.body;
	}

	public T remove(final int index) {
		if (index < 0 || index >= size) {
			throw new MyIndexOutOfBoundsException("Index out of bounds!");
		}

		T res;

		if (index == 0) {
			res = head.body;
			head = head.next;
			head.previous = null;
			size--;
			return res;
		}

		if (index == size - 1) {
			res = tail.body;
			tail = tail.previous;
			tail.next = null;
			size--;
			return res;
		}

		Node<T> p = head;
		for (int i = 0; i < index; i++) {
			p = p.next;
		}
		res = p.body;
		p.previous.next = p.next;
		p.next.previous = p.previous;
		p = new Node<T>();
		size--;
		return res;
	}

	public void clear() {
		Node<T> p = head;
		while (p.next != null) {
			if (p.previous != null) {
				p.previous.next = null;
			}

			p.previous = null;
			p = p.next;
		}

		head = new Node<T>();
		tail = head;
		size = 0;
	}

	public boolean isEmpty() {
		return (size == 0);
	}

	public void set(final int index, final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		if (index < 0 || index >= size) {
			throw new MyIndexOutOfBoundsException("Index out of bounds!");
		}

		if (index == 0) {
			head.body = e;
		}

		if (index == size - 1) {
			tail.body = e;
		}

		Node<T> p = head;
		for (int i = 0; i < index; i++) {
			p = p.next;
		}
		p.body = e;
	}

	public int indexOf(final T o) {
		if (isEmpty()) {
			return -1;
		}

		Node<T> p = head;

		for (int i = 0; i < size; i++) {
			if (p.body.equals(o)) {
				return i;
			}
			p = p.next;
		}

		return -1;
	}

	public int size() {
		return size;
	}

	public T[] toArray() {
		if (size == 0) {
			return null;
		}

		T[] res = (T[]) new Object[size];
		Node<T> p = head;
		res[0] = p.body;
		int i = 1;
		while (p.next != null) {
			p = p.next;
			res[i] = p.body;
			i++;
		}
		return res;
	}

	public void addFirst(final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		Node<T> p = new Node<T>(e);
		p.next = head;
		head.previous = p;
		head = p;
		size++;
	};

	public void addLast(final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		Node<T> p = new Node<T>(e);
		p.previous = tail;
		tail.next = p;
		tail = p;
		size++;
	};

	T getFirst() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("List is empty!");
		}

		T res = head.body;

		return res;
	};

	T getLast() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("List is empty!");
		}

		T res = tail.body;

		return res;
	};

	T removeFirst() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("List is empty!");
		}

		T res = head.body;
		head = head.next;
		head.previous.next = null;
		head.previous = null;
		size--;

		return res;

	};

	T removeLast() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("List is empty!");
		}

		T res = tail.body;
		tail = tail.previous;
		tail.next.previous = null;
		tail.next = null;
		size--;

		return res;
	};

	public Iterator<T> iterator() {
		Iterator<T> it = new Iterator<T>() {

            private Node<T> currentElem = head;

            public boolean hasNext() {
                return (currentElem.next != null);
            }

            public T next() {
            	T res = currentElem.body;
            	currentElem = currentElem.next;
                return res;
            }

            public void remove() {
                // TODO Auto-generated method stub
            }
        };
        return it;
	}

	public Iterator<T> descendingIterator() {
		Iterator<T> it = new Iterator<T>() {

            private Node<T> currentElem = tail;

            public boolean hasNext() {
                return (currentElem.previous != null);
            }

            public T next() {
            	T res = currentElem.body;
            	currentElem = currentElem.previous;
                return res;
            }

            public void remove() {
                // TODO Auto-generated method stub
            }
        };
        return it;
	};

	public String toString() {
		if (isEmpty()) {
			throw new MyIndexOutOfBoundsException("List is empty!");
		}

		String res = "";
		Node<T> p = head;
		res += p.body.toString();
		while (p.next != null) {
			res += "; ";
			p = p.next;
			res += p.body.toString();			
		}

		return res;
	}

	public void offer(final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		add(e);
	}

	public T peek() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("Queue is empty!");
		}

		T o = head.body;
		return o;
	}

	public T poll() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("Queue is empty!");
		}

		return removeFirst();
	}

	public void push(final T e) {
		if (e == null) {
			throw new NullPointerException();
		}

		add(0, e);
	}

	public T pop() {
		if (size == 0) {
			throw new MyIndexOutOfBoundsException("Stack is empty!");
		}

		return removeFirst();
	}
}
